package p27_返回值处理器;

import cn.hutool.core.collection.ListUtil;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.*;
import org.springframework.web.util.UrlPathHelper;

import java.lang.reflect.Method;
import java.util.Collection;

/**
 * 返回值处理器
 * 对于不需要走视图渲染的返回值处理器，会将对象序列化为字符串放入请求体
 * 这个序列化的工作由消息转换器完成
 * 参数解析器，将请求体数据转为java对象也要用到消息转换器
 */
public class _ReturnValueHandler {

    static MyController controller = new MyController();

    public static void main(String[] args) throws Exception {
        //解析返回值 ---> ModelAndView
        testModelAndView();
        //解析返回值 ---> String
        testString();
        //解析返回值 ---> @ModelAttribute
        testModelAttribute();
        //解析返回值 ---> 普通对象
        testModelAttribute1();
        //解析返回值 ---> HttpEntity
        testHttpEntity();
        //解析返回值 ---> HttpHeader
        testHttpHeader();
        //解析返回值 ---> @ResponseBody
        testResponseBody();
    }

    /**
     * 解析返回值为ModelAndView的控制器方法
     * 会将返回值对象里填充的数据取出作为模型数据和视图名称，放入ModelAndViewContainer
     * 然后就可以根据这些模型数据，根据模板引擎渲染视图
     * 渲染时根据视图名称，拼接上前缀后缀，从而得到一个模板文件路径，解析这个模板文件，并将模型数据填充进模板，从而得到一个完整的html文本
     * 将这段文档放入响应体，浏览器得到响应后就可以正常渲染
     */
    public static void testModelAndView() throws Exception {
        Method test1 = controller.getClass().getDeclaredMethod("test1");
        HandlerMethod handlerMethod = new HandlerMethod(controller, test1);
        //获取返回值
        Object result = test1.invoke(controller);
        ModelAndViewContainer container = new ModelAndViewContainer();

        /**
         * 从返回值处理器组合器中找一个合适的处理器，来对返回值result进行处理
         * 会遍历所有处理器，逐一调用supportsReturnType方法，来是否有处理器可以进行处理
         * handleReturnValue方法又会再次遍历，得到那个合适的返回值处理器来进行处理
         * 最终将数据填充进ModelAndViewContainer，然后处理器适配器再根据ModelAndViewContainer进行其他操作
         */
        if (getReturnValueHandlerComposite().supportsReturnType(handlerMethod.getReturnType())) {
            getReturnValueHandlerComposite().handleReturnValue(result, handlerMethod.getReturnType(), container, new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()));
            System.out.println(container.getModel());
            System.out.println(container.getViewName());
            //接着渲染视图，得到html5文本，放入响应体
        }

        System.out.println("=======================");
    }

    /**
     * 解析返回值为String的控制器方法
     * 只会将返回值作为视图名称
     */
    public static void testString() throws Exception {
        Method method = controller.getClass().getDeclaredMethod("test2");
        HandlerMethod methodHandle = new HandlerMethod(controller, method);
        //获取返回值
        Object result = method.invoke(controller);
        ModelAndViewContainer container = new ModelAndViewContainer();

        if (getReturnValueHandlerComposite().supportsReturnType(methodHandle.getReturnType())) {
            getReturnValueHandlerComposite().handleReturnValue(result, methodHandle.getReturnType(), container, new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()));
            //得到视图名称，根据视图名称得到模板文件
            System.out.println(container.getViewName());
        }
        System.out.println("=======================");
    }

    /**
     * 解析加了@ModelAttribute的控制器方法
     * @ModelAttribute会将返回值作为模型数据
     */
    public static void testModelAttribute() throws Exception {
        Method method = controller.getClass().getDeclaredMethod("test3");
        HandlerMethod methodHandle = new HandlerMethod(controller, method);
        //获取返回值
        Object result = method.invoke(controller);
        ModelAndViewContainer container = new ModelAndViewContainer();

        if (getReturnValueHandlerComposite().supportsReturnType(methodHandle.getReturnType())) {
            MockHttpServletRequest request = new MockHttpServletRequest();
            //设置请求的路径映射
            request.setRequestURI("/test3");
            UrlPathHelper.defaultInstance.resolveAndCacheLookupPath(request);

            getReturnValueHandlerComposite().handleReturnValue(result, methodHandle.getReturnType(), container, new ServletWebRequest(request, new MockHttpServletResponse()));
            //得到视图名称，根据视图名称得到模板文件
            System.out.println(container.getViewName());
            System.out.println(container.getModel());
        }
        System.out.println("=======================");
    }

    /**
     * 解析返回值为一个对象的控制器方法，没有@ModelAttribute，效果跟加了类似
     */
    public static void testModelAttribute1() throws Exception {
        Method method = controller.getClass().getDeclaredMethod("test4");
        HandlerMethod methodHandle = new HandlerMethod(controller, method);
        //获取返回值
        Object result = method.invoke(controller);
        ModelAndViewContainer container = new ModelAndViewContainer();

        if (getReturnValueHandlerComposite().supportsReturnType(methodHandle.getReturnType())) {
            getReturnValueHandlerComposite().handleReturnValue(result, methodHandle.getReturnType(), container, new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()));
            System.out.println(container.getModel());
        }

        System.out.println("=======================");
    }

    /**
     * 解析返回值为HttpEntity的控制器方法，不会走视图渲染流程
     * 解析该返回值的返回值处理器，会将请求处理状态设为true，表示请求处理了，不会走视图渲染流程了
     * HttpEntity表示响应的全部内容，包括响应头，响应体
     */
    public static void testHttpEntity() throws Exception {
        Method method = controller.getClass().getDeclaredMethod("test5");
        HandlerMethod methodHandle = new HandlerMethod(controller, method);
        //获取返回值
        Object result = method.invoke(controller);
        ModelAndViewContainer container = new ModelAndViewContainer();

        if (getReturnValueHandlerComposite().supportsReturnType(methodHandle.getReturnType())) {
            MockHttpServletResponse res = new MockHttpServletResponse();
            getReturnValueHandlerComposite().handleReturnValue(result, methodHandle.getReturnType(), container, new ServletWebRequest(new MockHttpServletRequest(), res));
            //对于一些返回值处理器，比如解析@ResponseBody的处理器，会将container的requestHandled状态设为true(p20的第四节演示了)，表示请求已经完结，不用走视图解析器
            if(container.isRequestHandled()) {
                System.out.println("不用渲染视图");
                System.out.println("响应体内容 ---> " + new String(res.getContentAsByteArray()));
            } else {
                System.out.println("渲染视图");
            }
        }
        System.out.println("=======================");
    }

    /**
     * 解析返回值为HttpHeader的控制器方法，不会走视图渲染流程
     * 解析该返回值的返回值处理器，会将请求处理状态设为true，表示请求处理了，不会走视图渲染流程了
     * HttpEntity表示响应的响应头
     */
    public static void testHttpHeader() throws Exception {
        Method method = controller.getClass().getDeclaredMethod("test6");
        HandlerMethod methodHandle = new HandlerMethod(controller, method);
        //获取返回值
        Object result = method.invoke(controller);
        ModelAndViewContainer container = new ModelAndViewContainer();

        if (getReturnValueHandlerComposite().supportsReturnType(methodHandle.getReturnType())) {
            MockHttpServletResponse res = new MockHttpServletResponse();
            getReturnValueHandlerComposite().handleReturnValue(result, methodHandle.getReturnType(), container, new ServletWebRequest(new MockHttpServletRequest(), res));
            if(container.isRequestHandled()) {
                System.out.println("不用渲染视图");
                System.out.println("响应头");
                Collection<String> headerNames = res.getHeaderNames();
                for (String name : headerNames) {
                    System.out.println(name + "::" +res.getHeader(name));
                }
            } else {
                System.out.println("渲染视图");
            }
        }

        System.out.println("=======================");
    }

    /**
     * 解析被@ResponseBody修饰的控制器方法，不会走视图渲染流程
     * 表示将返回对象化为json数据放入响应体
     * 解析该返回值的返回值处理器，会将请求处理状态设为true，表示请求处理了，不会走视图渲染流程了
     */
    public static void testResponseBody() throws Exception {
        Method method = controller.getClass().getDeclaredMethod("test7");
        HandlerMethod methodHandle = new HandlerMethod(controller, method);
        //获取返回值
        Object result = method.invoke(controller);
        ModelAndViewContainer container = new ModelAndViewContainer();

        if (getReturnValueHandlerComposite().supportsReturnType(methodHandle.getReturnType())) {
            MockHttpServletResponse res = new MockHttpServletResponse();
            getReturnValueHandlerComposite().handleReturnValue(result, methodHandle.getReturnType(), container, new ServletWebRequest(new MockHttpServletRequest(), res));
            if(container.isRequestHandled()) {
                System.out.println("不用渲染视图");
                System.out.println(new String(res.getContentAsByteArray()));
            } else {
                System.out.println("渲染视图");
            }
        }

        System.out.println("=======================");
    }

    //该方法提供包含了多个返回值处理器的返回值处理器组合器
    public static HandlerMethodReturnValueHandlerComposite getReturnValueHandlerComposite() {
        HandlerMethodReturnValueHandlerComposite composite = new HandlerMethodReturnValueHandlerComposite();
        composite.addHandler(new ModelAndViewMethodReturnValueHandler());
        composite.addHandler(new ViewNameMethodReturnValueHandler());
        composite.addHandler(new ServletModelAttributeMethodProcessor(false));
        composite.addHandler(new HttpEntityMethodProcessor(ListUtil.of(new MappingJackson2HttpMessageConverter())));
        composite.addHandler(new HttpHeadersReturnValueHandler());
        composite.addHandler(new RequestResponseBodyMethodProcessor(ListUtil.of(new MappingJackson2HttpMessageConverter())));
        composite.addHandler(new ServletModelAttributeMethodProcessor(true));
        return composite;
    }
}
